

use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
use redis::{Commands,Pipeline};
use shq_common::prelude::{empl::EmployeeCache, get_my_pool, get_redis_cli, Executor, FlowRunProps};

use super::TaskStatus;
use sqlx::Row;
const HKEY_FLOW_USERS: &str = "flow_step_users";
const HKEY_FLOW_INFO: &str = "flow_info";
const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
pub(crate) struct FlowInfo {
    pub(crate) wfid: u32,
    pub(crate) step_id: u8,
    pub(crate) state: u8,
    pub(crate) version: u8,
}
impl From<FlowInfo> for FlowRunProps {
    fn from(fi: FlowInfo) -> Self {
        FlowRunProps {
            wfid: fi.wfid,
            step_id: fi.step_id,
            state: fi.state.into(),
            version: fi.version,
        }
    }
}
impl FlowInfo {
    pub(crate) fn new(wfid: u32, step_id: u8, state: u8, version: u8) -> Self {
        Self {
            wfid,
            step_id,
            state,
            version,
        }
    }
    pub(crate) fn from_vec(vals: Vec<(u8, u32)>) -> Self {
        let mut wfid: u32 = 0;
        let mut step_id: u8 = 0;
        let mut state: u8 = 0;
        let mut version: u8 = 0;
        for (k, v) in vals {
            match k {
                0 => wfid = v,
                1 => step_id = v as u8,
                2 => state = v as u8,
                3 => version = v as u8,
                _ => (),
            }
        }
        Self {
            wfid,
            step_id,
            state,
            version,
        }
    }
}
#[derive(Debug,Clone,PartialEq, Eq)]
pub(crate) enum SaveType {
    Update,
    Insert,
    None,
}
#[derive(Debug,Clone,PartialEq, Eq)]
pub struct ExecutorInfo {
    pub user_id: u32,
    pub task_state: TaskStatus,
    pub start_time: Option<DateTime<Local>>,
    pub submit_time: Option<DateTime<Local>>,
    pub feed_back: Option<String>,
    pub (crate) save_type: SaveType,
}
impl From<ExecutorInfo> for Executor {
    fn from(ei: ExecutorInfo) -> Self {
        let mut ec = EmployeeCache::new().unwrap();
        Self {
            user_id: ei.user_id,
            user_name: ec.get_empl_name(ei.user_id),
            task_state: ei.task_state,
            start_time: ei.start_time.map(|v| v.format("%Y-%m-%d %H:%M:%S").to_string()),
            submit_time: ei.submit_time.map(|v| v.format("%Y-%m-%d %H:%M:%S").to_string()),
            feed_back: ei.feed_back,
        }
    }
}
impl ExecutorInfo {
    pub(crate) fn new(
        user_id: u32,
        task_state: TaskStatus,
        start_time: Option<DateTime<Local>>,
        submit_time: Option<DateTime<Local>>,
        feed_back: Option<String>,
        save_type: SaveType
    ) -> Self {
        Self {
            user_id,
            task_state,
            start_time,
            submit_time,
            feed_back,
            save_type
        }
    }
    pub(crate) fn from_str(user_id: u32, line: &str) -> anyhow::Result<Self> {
        let ds:Vec<&str> = line.split('|').collect();
        if ds.len() < 4 {
            return Err(anyhow::anyhow!("invalid user info"));
        }
        let state = ds[0].parse::<TaskStatus>()?;
        let start_time = if ds[1].is_empty() { None } else { Some(ds[1]) };
        let start_time = if let Some(st) = start_time {
            let dt=NaiveDateTime::parse_from_str(st, DATE_TIME_FORMAT)?;
            Some(Local.from_local_datetime(&dt).unwrap())
        } else {
            None
        };
        let submit_time = if ds[2].is_empty() { None } else { Some(ds[2]) };
        let submit_time = if let Some(st) = submit_time {
            let dt=NaiveDateTime::parse_from_str(st, DATE_TIME_FORMAT)?;
            Some(Local.from_local_datetime(&dt).unwrap())
        } else {
            None
        };
        let feed_back = if ds[3].is_empty() { None } else { Some(ds[3].to_owned()) };
        Ok(Self {
            user_id,
            task_state: state,
            start_time,
            submit_time,
            feed_back,
            save_type: SaveType::None
        })
    }
    pub(crate) fn to_string_no_uid(&self) -> String {
        let start_time = self
            .start_time
            .map(|v| v.format("%Y-%m-%d %H:%M:%S").to_string())
            .unwrap_or_default();
        let submit_time = self
            .submit_time
            .map(|v| v.format("%Y-%m-%d %H:%M:%S").to_string())
            .unwrap_or_default();
        let feed_back = self.feed_back.as_deref().unwrap_or_default();
        format!("{}|{start_time}|{submit_time}|{feed_back}",self.task_state)
    }
}
pub(crate) struct FlowCache {
    rcon: redis::Connection,
    run_id: u64,
}
impl FlowCache {
    pub(crate) fn from(run_id: u64) -> anyhow::Result<Self> {
        let rcon = get_redis_cli().get_connection()?;
        Ok(Self { rcon, run_id })
    }
    pub(crate) async fn get_flow_info(&mut self) -> anyhow::Result<FlowInfo> {
        let hkey = format!("{}:{}", HKEY_FLOW_INFO, self.run_id);
        let exist:bool=self.rcon.exists(&hkey)?;
        let info=if exist {
            let vals: Vec<(u8, u32)> = self.rcon.hgetall(&hkey)?;
            FlowInfo::from_vec(vals)
        }else{
            self.load_flow_info().await?
        };
        Ok(info)
    }
    pub(crate) fn del_flow_info(&mut self) -> anyhow::Result<()> {
        let hkey = format!("{}:{}", HKEY_FLOW_INFO, self.run_id);
        self.rcon.del(hkey)?;
        Ok(())
    }
    pub(crate) fn set_flow_info(
        &mut self,
        wfid: u32,
        step_id: u8,
        state: u8,
        version: u8,
    ) -> anyhow::Result<()> {
        let hkey = format!("{}:{}", HKEY_FLOW_INFO, self.run_id);
        let vals = &[
            (0_u8, wfid),
            (1, step_id as u32),
            (2, state as u32),
            (3, version as u32),
        ];
        self.rcon.hset_multiple(&hkey, vals)?;
        self.rcon.expire(hkey, 60 * 60 * 24 * 3)?;
        Ok(())
    }
    pub(crate) fn get_step_users(&mut self, step_id: u8) -> anyhow::Result<Vec<ExecutorInfo>> {
        let hkey = format!("{}:{}:{step_id}", HKEY_FLOW_USERS, self.run_id);
        if !self.rcon.exists(&hkey)? {
            return Ok(Vec::new());
        }
        let mut res:Vec<ExecutorInfo> =Vec::new();
        let user_infos: Vec<(u32, String)> =self.rcon.hgetall(hkey)?;
        for (user_id, info) in user_infos.into_iter() {
            let sui=ExecutorInfo::from_str(user_id, &info)?;
            res.push(sui);
        }
        Ok(res)
    }
    pub(crate) fn del_step_users(&mut self, step_id: u8) -> anyhow::Result<()> {
        let hkey = format!("{}:{}:{step_id}", HKEY_FLOW_USERS, self.run_id);
        let _: () = self.rcon.del(hkey)?;
        Ok(())
    }
    pub(crate) fn set_step_users(
        &mut self,
        step_id: u8,
        user_infos: &Vec<ExecutorInfo>,
    ) -> anyhow::Result<()> {
        let hkey = format!("{}:{}:{step_id}", HKEY_FLOW_USERS, self.run_id);
        let mut pipe=Pipeline::new();
        for user_info in user_infos.iter() {
            pipe.hset(&hkey, user_info.user_id, user_info.to_string_no_uid());
        }
        pipe.expire(hkey, 60 * 60 * 24 * 3);
        pipe.exec(&mut self.rcon)?;
        Ok(())
    }
    async fn load_flow_info(&mut self) -> anyhow::Result<FlowInfo> {
        let sql = "select count(*) from flow_run where run_id=?";
        let count: i64 = sqlx::query_scalar(sql)
            .bind(self.run_id)
            .fetch_one(get_my_pool())
            .await?;
        let sql = if count > 0 {
            "select wfid,step_id,state,version from flow_run where run_id=?"
        } else {
            "select wfid,step_id,state,version from flow_run_his where run_id=?"
        };
        let row = sqlx::query(sql)
            .bind(self.run_id)
            .fetch_one(get_my_pool())
            .await?;
        let wfid: u32 = row.try_get(0)?;
        let curr_step_id: u8 = row.try_get(1)?;
        let state: u8 = row.try_get(2)?;
        let version: u8 = row.try_get(3)?;
        self.set_flow_info(wfid, curr_step_id, state, version)?;
        Ok(FlowInfo::new(wfid, curr_step_id, state, version))
    }
}
